package cn.snowsoft.iot.module.cps.service.equipment;

import cn.snowsoft.iot.framework.common.pojo.CommonResult;
import cn.snowsoft.iot.module.cps.api.equipment.dto.EquipmentRelateWarningDTO;
import cn.snowsoft.iot.module.cps.controller.admin.equipment.vo.EquipmentPage;
import cn.snowsoft.iot.module.cps.controller.admin.equipment.vo.EquipmentPageVO;
import cn.snowsoft.iot.module.cps.controller.admin.home.vo.EquipmentAgreementTypeVO;
import cn.snowsoft.iot.module.cps.controller.admin.home.vo.EquipmentStatusVO;
import cn.snowsoft.iot.module.cps.dal.dataobject.equipment.EquipmentDO;
import cn.snowsoft.iot.module.cps.dal.dataobject.equipment.EquipmentPointDO;
import cn.snowsoft.iot.module.cps.dal.dataobject.product.ProductDO;
import cn.snowsoft.iot.module.cps.dal.mysql.equipment.EquipmentMapper;
import cn.snowsoft.iot.module.cps.ekuiperClient.MonitorClient;
import cn.snowsoft.iot.module.cps.mqtt.Authentication;
import cn.snowsoft.iot.module.cps.mqtt.InitCpsServer;
import cn.snowsoft.iot.module.cps.service.cpsRule.CpsRuleService;
import cn.snowsoft.iot.module.cps.service.product.ProductService;
import cn.snowsoft.iot.module.warning.enums.api.config.AdminConfigApi;
import cn.snowsoft.iot.module.warning.enums.api.config.vo.ConfigCountVO;
import com.alibaba.cloud.commons.lang.StringUtils;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.xxdb.data.DolphinDBUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class EquipmentServiceImpl extends ServiceImpl<EquipmentMapper, EquipmentDO> implements EquipmentService {

    @Resource
    private EquipmentPointService pointService;
    @Autowired
    @Lazy
    private DolphinDBUtils dolphindbUtils;

    @Autowired
    private MonitorClient monitorClient;

    @Autowired
    private ProductService productService;

    @Resource
    private AdminConfigApi configApi;

    @Resource
    private CpsRuleService cpsRuleService;

    @Value("${ekuiper.url}")
    private String ekuiperUrl;

    @Value("${ekuiper.ws}")
    private String ws;

    @Value("${ekuiper.sql}")
    private String ekuiperSql;

    @Value("${ekuiper.actions.path}")
    private String ekuiperWebSocketPath;
    private String deviceQRCode;
    @Override
    public IPage<EquipmentPage> selectEquipmentPage(EquipmentPageVO equipment) {
        IPage<EquipmentPage> page = new Page<>(equipment.getPageNo(), equipment.getPageSize());
        IPage<EquipmentPage> equipmentDOIPage = baseMapper.selectEquipmentPage(page, equipment);
        List<EquipmentPage> records = equipmentDOIPage.getRecords();
        List<String> equipmentCodeList = new ArrayList<>();
        records.forEach(item -> {
            equipmentCodeList.add(item.getEquipmentCode());
        });
        if (equipmentCodeList.size() > 0) {
            List<ConfigCountVO> warningCount = configApi.getWarningCount(equipmentCodeList);
            Map<String, Integer> configCountMap = new HashMap<>();
            for (ConfigCountVO configCount : warningCount) {
                configCountMap.put(configCount.getEquipmentCode(), configCount.getWarningCount());
            }
            for (EquipmentPage equipmentPage : records) {
                Integer count = configCountMap.get(equipmentPage.getEquipmentCode());
                if (count != null) {
                    equipmentPage.setWarningCount(count);
                } else {
                    equipmentPage.setWarningCount(0);
                }
            }
        }
        page.setRecords(records);
        return equipmentDOIPage;
    }

    @Override
    public Boolean enableEquipment(List<Long> ids) {
        List<EquipmentDO> equipmentDOS = this.listByIds(ids);
        if (ObjectUtils.isNotEmpty(equipmentDOS)) {
            List<String> productCodeList = new ArrayList<>();
            for (EquipmentDO equipment : equipmentDOS) {
                if("1".equals(equipment.getIsHistory())){
                    createDolphinTable(equipment);
                }
                if("0".equals(equipment.getIsHistory())){
                    deleteDolphinTable(equipment);
                }
                String calcStreamSql = "CREATE STREAM streamParam () WITH (DATASOURCE=\"topicParam\", CONF_KEY=\"rabbit\", TYPE=\"mqtt\", SHARED=\"true\", FORMAT=\"json\");";
                String streamParam = "AttributePush_" + equipment.getEquipmentCode();
                JSONObject calcStream = new JSONObject();
                calcStream.put("sql", calcStreamSql.replace("streamParam", streamParam).replace("topicParam", equipment.getAttributePush()));
                MonitorClient.streamCreation(calcStream, ekuiperUrl);
                equipment.setIsEnable(1);
                if (updateById(equipment)) {
                    productCodeList.add(equipment.getProductCode());
                    InitCpsServer.deviceCodeAttributeTopic.put(equipment.getEquipmentCode(), equipment.getAttributePush());
                    InitCpsServer.deviceCodeFeatureTopic.put(equipment.getEquipmentCode(), equipment.getFeatureIssued());
                }
            }
            try {
                cpsRuleService.enabledOrDisabledRules(ids, productCodeList, 1);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return true;
    }

    @Override
    public Boolean disenableEquipment(List<Long> ids) {
        List<EquipmentDO> equipmentDOS = this.listByIds(ids);
        if (ObjectUtils.isNotEmpty(equipmentDOS)) {
            List<String> productCodeList = new ArrayList<>();

            for (EquipmentDO equipment : equipmentDOS) {
                productCodeList.add(equipment.getProductCode());
                MonitorClient.ruleDelete("monitor-" + equipment.getEquipmentCode(), ekuiperUrl);
                MonitorClient.streamDelete("AttributePush_" + equipment.getEquipmentCode(), ekuiperUrl);
                InitCpsServer.deviceCodeAttributeTopic.remove(equipment.getEquipmentCode());
                InitCpsServer.deviceCodeFeatureTopic.remove(equipment.getEquipmentCode());
            }
            try {
                cpsRuleService.enabledOrDisabledRules(ids, productCodeList, 0);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            return lambdaUpdate().in(EquipmentDO::getId, ids).set(EquipmentDO::getIsEnable, 0).update();
        }
        return true;
    }

    @Override
    public Boolean removeEquipment(EquipmentPage equipment) {
        if (StringUtils.isNotBlank(equipment.getUserAccount())) {
            try {
                Authentication.removeAuthentication(equipment);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        Long id = equipment.getId();
        pointService.removePoint(id);
        baseMapper.deleteById(id);
        return true;
    }

    @Override
    public Object restart() {
        List<EquipmentDO> lists = lambdaQuery().eq(EquipmentDO::getIsEnable, 1).list();
        if (lists.isEmpty()) {
            return false;
        }
        List<Long> ids = lists.stream().map(EquipmentDO::getId).collect(Collectors.toList());

        LambdaUpdateWrapper<EquipmentDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
        lambdaUpdateWrapper.set(EquipmentDO::getIsEnable, 0).in(EquipmentDO::getId, ids);
        boolean updateSuccess = this.update(lambdaUpdateWrapper);

        if (updateSuccess) {
            enableEquipment(ids);
        } else {
            log.error("Failed to disable equipment");
            return false;
        }
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public CommonResult submit(EquipmentDO equipment) {
        List<EquipmentDO> equipmentList = checkCode(equipment);
        if (equipmentList.size() > 0) {
            return CommonResult.error(207, "设备编码已存在");
        }
        if (equipment.getId() == null) {
            equipment.setEquipmentAscription("none");
            equipment.setQrCode(twoDimensionalCode("{equipmentCode:"+equipment.getEquipmentCode()+"}"));
        }
        if (equipment.getAgreementType() == null) {
            equipment.setAgreementType("");
        }
        saveOrUpdate(equipment);
        ProductDO productDO = productService.getByCode(equipment.getProductCode());
        pointService.coveragePoint(equipment.getId(), productDO.getId());
        return CommonResult.success(equipment);
    }
    /**
     * 将JSONObject转换为请求路径的方法
     *
     * @param jsonObject
     * @return
     */
    private static String convertToRequestPath(cn.hutool.json.JSONObject jsonObject) {
        StringBuilder pathBuilder = new StringBuilder();
        for (String key : jsonObject.keySet()) {
            Object value = jsonObject.get(key);
            try {
                value = URLEncoder.encode(value.toString(), "UTF-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            pathBuilder.append(key).append("=").append(value).append("&");
        }
        pathBuilder.deleteCharAt(pathBuilder.length() - 1);

        return pathBuilder.toString();
    }
    public String twoDimensionalCode(String code) {
        cn.hutool.json.JSONObject jsonObject = new cn.hutool.json.JSONObject(code);
        String convertToRequestPath = convertToRequestPath(jsonObject);
        String data = deviceQRCode + convertToRequestPath;

        int width = 300;
        int height = 300;

        try {
            QRCodeWriter writer = new QRCodeWriter();
            BitMatrix bitMatrix = writer.encode(data, BarcodeFormat.QR_CODE, width, height);

            BufferedImage image = MatrixToImageWriter.toBufferedImage(bitMatrix);

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ImageIO.write(image, "png", baos);

            byte[] imageBytes = baos.toByteArray();

            String base64Image = Base64.getEncoder().encodeToString(imageBytes);

            return base64Image;
        } catch (Exception e) {
            System.err.println("生成二维码时出现异常：" + e.getMessage());
        }
        return null;
    }



    @Override
    public EquipmentDO detailMonitor(Long id) {
        EquipmentDO equipmentDO = getById(id);
        JSONObject actionParam = new JSONObject();
        actionParam.put("path", ekuiperWebSocketPath + equipmentDO.getEquipmentCode());
        actionParam.put("insecureSkipVerify", true);
        actionParam.put("sendSingle", true);

        JSONObject action = new JSONObject();
        action.put("websocket", actionParam);

        JSONArray actions = new JSONArray();
        actions.add(action);

        JSONObject data = new JSONObject();
        data.put("id", "monitor-" + equipmentDO.getEquipmentCode());
        data.put("name", "数据监控-" + equipmentDO.getEquipmentName());
        data.put("sql", ekuiperSql.replace("param", "AttributePush_" + equipmentDO.getEquipmentCode()));
        data.put("actions", actions);
        monitorClient.ruleCreation(data, ekuiperUrl);
        equipmentDO.setWebSocket(ws + ekuiperWebSocketPath + equipmentDO.getEquipmentCode());
        return equipmentDO;
    }

    @Override
    public IPage<EquipmentPage> selectEquipmentPageByProductCode(EquipmentPageVO equipment) {
        IPage<EquipmentPage> page = new Page<>(equipment.getPageNo(), equipment.getPageSize());
        IPage<EquipmentPage> equipmentDOIPage = baseMapper.selectEquipmentPageByProductCode(page, equipment);
        List<EquipmentPage> records = equipmentDOIPage.getRecords();
        List<Long> ids = new ArrayList<>();
        records.forEach(item -> {
            ids.add(item.getId());
        });
        if (ids.size() > 0) {
            Map<Long, Integer> configCountMap = new HashMap<>();
            for (EquipmentPage equipmentPage : records) {
                Integer count = configCountMap.get(equipmentPage.getId());
                if (count != null) {
                    equipmentPage.setWarningCount(count);
                } else {
                    equipmentPage.setWarningCount(0);
                }
            }
        }
        page.setRecords(records);
        return equipmentDOIPage;
    }

    private List<EquipmentDO> checkCode(EquipmentDO equipment) {
        LambdaQueryWrapper<EquipmentDO> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        if (equipment.getEquipmentCode().isEmpty()) {
            throw new RuntimeException("设备编码不能为空");
        }
        lambdaQueryWrapper.eq(EquipmentDO::getEquipmentCode, equipment.getEquipmentCode());
        if (equipment.getId() != null) {
            lambdaQueryWrapper.ne(EquipmentDO::getId, equipment.getId());
        }
        return baseMapper.selectList(lambdaQueryWrapper);
    }

    /**
     * 根据设备的模型信息构造脚本需要的参数并执行
     *
     * @param equipment  equipment
     * @return boolean
     */
    public boolean createDolphinTable(EquipmentDO equipment) {
        JSONArray jsonArray = new JSONArray();
        Map<String, Integer> type = new HashMap<>();
        type.put("bool", 1);
        type.put("float", 16);
        type.put("string", 18);

        List<EquipmentPointDO> equipmentPoints = pointService.lambdaQuery().eq(EquipmentPointDO::getEquipmentId, equipment.getId()).list();

        equipmentPoints.forEach(e -> {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("name", e.getPointCode());
            jsonObject.put("type", type.get(e.getDataType()));
            jsonArray.add(jsonObject);
        });

        boolean dolph = dolphindbUtils.createDeviceTable(equipment, jsonArray);
        log.info("dolphinDB建表执行结果: {}", dolph);
        return dolph;
    }


    /**
     * 根据设备信息删除dolphin表
     *
     * @param equipment  equipment
     * @return boolean
     */
    public boolean deleteDolphinTable(EquipmentDO equipment) {
        boolean dolph = dolphindbUtils.deleteDeviceTable(equipment);
        log.info("dolphinDB删除pt_"+equipment.getEquipmentCode()+"表执行结果: {}", dolph);
        return dolph;
    }

    @Override
    public List<EquipmentAgreementTypeVO> getAgreementTypeCount() {
        return baseMapper.getAgreementTypeCount();
    }

    @Override
    public List<EquipmentRelateWarningDTO> getEquipmentName(List<Long> equipmentIds) {
        return baseMapper.getEquipmentName(equipmentIds);
    }

    @Override
    public List<EquipmentStatusVO> getIsEnableCount() {
        return baseMapper.getIsEnableCount();
    }

    @Override
    public CommonResult cancelPassage(String id) {
        boolean status = baseMapper.cancelPassage(id);
        return CommonResult.success(status);
    }


    @Override
    public List<EquipmentDO> getByIds(String ids) {
        List<String> list = Arrays.asList(ids.split(","));
        return baseMapper.selectList(new LambdaQueryWrapper<EquipmentDO>().in(EquipmentDO::getId, list));
    }

    @Override
    public CommonResult getChildEquipment(String equipmentCode) {
        List<EquipmentDO> equipmentDOS = baseMapper.getChildEquipment(equipmentCode);
        if (ObjectUtils.isNotEmpty(equipmentDOS)) {
            return CommonResult.success(true);
        } else {
            return CommonResult.success(false);
        }
    }
}
